package com.taomus.mytools.helper.xml;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.taomus.mytools.helper.StringHelper;
import com.taomus.mytools.helper.xml.annotations.Attribute;
import com.taomus.mytools.helper.xml.annotations.Column;
import com.taomus.mytools.helper.xml.annotations.Element;

public class FromXmlMapping {

	final static Logger LOG = LoggerFactory.getLogger(FromXmlMapping.class);

	public FromXmlMapping() {
		super();
	}

	/**
	 * 将类字段名拼接成 xpath
	 * 
	 * @param doc
	 * @param obj
	 * @param fields
	 * @param xpath
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	public void mapping(Document doc, Object obj, Field[] fields, String xpath)
			throws InstantiationException, IllegalAccessException {
		for (Field field : fields) {
			field.setAccessible(true);
			Attribute attr = field.getAnnotation(Attribute.class);
			Element elm = field.getAnnotation(Element.class);
			String nodeName = field.getName();
			if (attr != null) {
				nodeName = StringHelper.defaultIfEmpty(attr.value(),field.getName());
				StringBuffer path = new StringBuffer(xpath);
				if (xpath != null && xpath.length() > 1) {
					path.append("/");
				}
				path.append("@");
				path.append(nodeName);
				nodeName = path.toString();
				element(doc, obj, field, nodeName);
			}
			if (elm != null) {
				nodeName = StringHelper.defaultIfEmpty(elm.value(),field.getName());
				StringBuffer path = new StringBuffer(xpath);
				if (xpath != null && xpath.length() > 1) {
					path.append("/");
				}
				path.append(nodeName);
				nodeName = path.toString();
				element(doc, obj, field, nodeName);
			}

		}
	}

	/**
	 * xml元素内容到类字段的赋值
	 * 
	 * @param doc
	 * @param instance
	 * @param field
	 * @param xpath
	 * @return
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	@SuppressWarnings("unchecked")
	private Object element(Document doc, Object instance, Field field, String xpath)
			throws InstantiationException, IllegalAccessException {
		List<Node> node = new ArrayList<>();
		LOG.info(xpath);
		if (xpath.startsWith("/")) {
			node = (List<Node>) doc.selectNodes(xpath);
		} else {
			node.add(doc.selectSingleNode(xpath));
		}
		if (node.size() > 0)
			switch (field.getType().getName()) {
			case "java.lang.String":
				try {
					field.set(instance, node.get(0).getStringValue());
				} catch (IllegalArgumentException | IllegalAccessException e) {
					e.printStackTrace();
				}
				break;
			case "int":
			case "java.lang.Integer":
				try {
					field.set(instance, Integer.valueOf(node.get(0).getStringValue()));
				} catch (IllegalArgumentException | IllegalAccessException e) {
					e.printStackTrace();
				}
				break;
			case "short":
			case "java.lang.Short":
				try {
					field.set(instance, Short.valueOf(node.get(0).getStringValue()));
				} catch (IllegalArgumentException | IllegalAccessException e) {
					e.printStackTrace();
				}
				break;
			case "long":
			case "java.lang.Log":
				try {
					field.set(instance, Long.valueOf(node.get(0).getStringValue()));
				} catch (IllegalArgumentException | IllegalAccessException e) {
					e.printStackTrace();
				}
				break;
			case "float":
			case "java.lang.Float":
				try {
					field.set(instance, Float.valueOf(node.get(0).getStringValue()));
				} catch (IllegalArgumentException | IllegalAccessException e) {
					e.printStackTrace();
				}
				break;
			case "double":
			case "java.lang.Double":
				try {
					field.set(instance, Double.valueOf(node.get(0).getStringValue()));
				} catch (IllegalArgumentException | IllegalAccessException e) {
					e.printStackTrace();
				}
				break;
			case "char":
			case "java.lang.Character":
				try {
					field.set(instance, Character.valueOf(node.get(0).getStringValue().toCharArray()[0]));
				} catch (IllegalArgumentException | IllegalAccessException e) {
					e.printStackTrace();
				}
				break;
			case "boolean":
			case "java.lang.Boolean":
				try {
					field.set(instance, Long.valueOf(node.get(0).getStringValue()));
				} catch (IllegalArgumentException | IllegalAccessException e) {
					e.printStackTrace();
				}
				break;
			case "java.util.List":
			case "java.util.ArrayList":
				listElement(doc, instance, field, node, xpath);
				break;
			default:
				Object obj2 = field.getType().newInstance();
				mapping(doc, obj2, field.getType().getDeclaredFields(), xpath);
				field.set(instance, obj2);
			}
		return "";
	}

	/**
	 * 处理类中的list字段
	 * 
	 * @param doc
	 * @param instance
	 * @param field
	 * @param nodes
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	private void listElement(Document doc, Object instance, Field field, List<Node> nodes, String xpath)
			throws IllegalArgumentException, IllegalAccessException {
		ParameterizedType pt = (ParameterizedType) field.getGenericType();
		String typeName = pt.getActualTypeArguments()[0].getTypeName();
		List<Object> result = new ArrayList<>();
		switch (typeName) {
		case "java.lang.String":
			for (Node node : nodes) {
				result.add(node.getStringValue());
			}
			break;
		case "java.lang.Integer":
			for (Node node : nodes) {
				result.add(Integer.valueOf(node.getStringValue()));
			}
			break;
		case "java.lang.Log":
			for (Node node : nodes) {
				result.add(Long.valueOf(node.getStringValue()));
			}
			break;
		case "java.lang.Short":
			for (Node node : nodes) {
				result.add(Short.valueOf(node.getStringValue()));
			}
			break;
		case "java.lang.Float":
			for (Node node : nodes) {
				result.add(Float.valueOf(node.getStringValue()));
			}
			break;
		case "java.lang.Double":
			for (Node node : nodes) {
				result.add(Double.valueOf(node.getStringValue()));
			}
			break;
		case "java.lang.Boolean":
			for (Node node : nodes) {
				result.add(Boolean.valueOf(node.getStringValue()));
			}
			break;
		default:
			try {
				for (Node node : nodes) {
					Class<?> cl = Thread.currentThread().getContextClassLoader().loadClass(typeName);
					try {
						Object obj = cl.newInstance();
						mapping(DocumentHelper.parseText(node.asXML()), obj, cl.getDeclaredFields(),
								"/" + field.getName());
						result.add(obj);
					} catch (InstantiationException | SecurityException | DocumentException e) {
						e.printStackTrace();
					}
				}
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
			break;
		}
		field.set(instance, result);
	}
}